package com.ygqh.baby.controller.os;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.mp.model.User;
import com.foxinmy.weixin4j.tuple.Text;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.ygqh.baby.ao.AppType;
import com.ygqh.baby.ao.ReceiveXml;
import com.ygqh.baby.constant.RedisConstant;
import com.ygqh.baby.controller.mobile.BaseController;
import com.ygqh.baby.po.YgPlatform;
import com.ygqh.baby.po.YgUser;
import com.ygqh.baby.po.YgUserAuths;
import com.ygqh.baby.redis.RedisDao;
import com.ygqh.baby.service.*;
import com.ygqh.baby.utils.AesException;
import com.ygqh.baby.utils.DateConvertUtils;
import com.ygqh.baby.utils.MsgCrypt;
import com.ygqh.baby.utils.SHA1Util;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author: sunshuo
 * @Date: 2019/4/16 12:41
 * @Version: 1.0
 */
@Controller
@RequestMapping("app/gateway")
public class GatewayController extends BaseController {

	private final static ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(3);

	@Autowired
	private YgPlatformService ygPlatformService;
	@Autowired
	private WxMpAuthorityService wxMpAuthorityService;
	@Autowired
	private YgUserService ygUserService;
	@Autowired
	private YgUserAuthsService ygUserAuthsService;
	@Autowired
	private YgWeixinMenuDayService ygWeixinMenuDayService;
	@Autowired
	private RedisDao redisDao;
	@Autowired
	private WxMpMessageService wxMpMessageService;

	/**
	 * 开发者接入验证
	 *
	 * @param request
	 * @param response
	 * @throws IOException
	 */
	@RequestMapping(method = RequestMethod.GET)
	public void get(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//消息来源可靠性验证
		String signature = request.getParameter("signature");// 微信加密签名
		String timestamp = request.getParameter("timestamp");// 时间戳
		String nonce = request.getParameter("nonce");       // 随机数
		//成为开发者验证
		String echostr = request.getParameter("echostr");
		// 来源
		String platformNo = request.getParameter("platformNo");
		YgPlatform platform = ygPlatformService.findByPlatformNo(platformNo, AppType.WeChat);
		JSONObject jsonObject = JSON.parseObject(platform.getRemark());
		String token = jsonObject.getString("token");
		//确认此次GET请求来自微信服务器，原样返回echostr参数内容，则接入生效，成为开发者成功，否则接入失败
		if (SHA1Util.encode(token, timestamp, nonce).equals(signature)) {
			response.getWriter().write(echostr);
		}
	}

	/**
	 * 接收消息
	 *
	 * @param request
	 * @param response
	 */

	@RequestMapping(method = RequestMethod.POST)
	public void post(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//消息来源可靠性验证
		String signature = request.getParameter("signature");// 微信加密签名
		String timestamp = request.getParameter("timestamp");// 时间戳
		String nonce = request.getParameter("nonce");       // 随机数
		// 加密消息签名
		String msgSignature = request.getParameter("msg_signature");
		// 来源
		String platformNo = request.getParameter("platformNo");
		YgPlatform platform = ygPlatformService.findByPlatformNo(platformNo, AppType.WeChat);
		JSONObject jsonObject = JSON.parseObject(platform.getRemark());
		String token = jsonObject.getString("token");
		String encodingAESKey = jsonObject.getString("encodingAESKey");

		System.out.println("token="+token+",timestamp="+timestamp+",nonce="+nonce+",signature="+signature);

		if (!SHA1Util.encode(token, timestamp, nonce).equals(signature)) {
			// 消息不可靠，直接返回
			System.out.println("gateway消息不可靠，直接返回");
			response.getWriter().write("");
			return;
		}
		//用户每次向公众号发送消息、或者产生自定义菜单点击事件时，响应URL将得到推送
		try {
			response.setCharacterEncoding("UTF-8");
			response.setContentType("text/xml");
			// 获取输入信息
			String inputXml = IOUtils.toString(request.getInputStream(), "utf-8");
			// 加密模式
			String encryptType = request.getParameter("encrypt_type");
			boolean isAes = "aes".equals(encryptType);
			if (isAes) {
				inputXml = decryptMsg(inputXml, msgSignature, timestamp, nonce, platform.getAppId(), token,
						encodingAESKey);
			}
			//处理输入消息，返回结果
			String outputXml = invoke(inputXml, platform);
			if (isAes && StringUtils.isNotEmpty(outputXml)) {
				outputXml = encryptMsg(outputXml, timestamp, nonce, platform.getAppId(), token, encodingAESKey);
			}
			System.out.println("处理后的消息：" + outputXml);
			response.getWriter().write(outputXml);
		} catch (Exception ex) {
			response.getWriter().write("");
			ex.printStackTrace();
		}
	}

	private String invoke(String inputXml, YgPlatform platform) {
		ReceiveXml receiveXml = XmlStream.fromXML(inputXml, ReceiveXml.class);
		String content = null;
		switch (receiveXml.getMsgType()) {
			case "event":
				content = handlerEventMsg(receiveXml, platform);
				break;
			default:
				autoReply(receiveXml.getFromUserName(), platform);
				break;
		}
		addWeixinMenuDay(receiveXml, platform);
		String outputXml;
		if (StringUtils.isNotBlank(content)) {
			outputXml = formatXmlAnswer(receiveXml.getFromUserName(), receiveXml.getToUserName(), content);
		} else {
			outputXml = "";
		}
		return outputXml;
	}

	private String formatXmlAnswer(String to, String from, String content) {
		return "<xml><ToUserName><![CDATA[" +
				to +
				"]]></ToUserName><FromUserName><![CDATA[" +
				from +
				"]]></FromUserName><CreateTime>" +
				System.currentTimeMillis() +
				"</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" +
				content +
				"]]></Content></xml>";
	}

	private String handlerEventMsg(ReceiveXml receiveXml, YgPlatform platform) {
		switch (receiveXml.getEvent()) {
			case "subscribe":
				// 关注
				subscribe(receiveXml.getFromUserName(), platform);
				autoReply(receiveXml.getFromUserName(), platform);
				break;
			case "SCAN":
				// 扫描二维码
				autoReply(receiveXml.getFromUserName(), platform);
				break;
			case "unsubscribe":
				// 取关
				unsubscribe(receiveXml.getFromUserName(), platform);
				break;
			case "VIEW":
				// 点击菜单
			default:
				break;
		}
		return "";
	}

	/**
	 * 添加微信公众号每日点击用户
	 * @param receiveXml
	 */
	private void addWeixinMenuDay(ReceiveXml receiveXml, YgPlatform platform) {
		try {
			String eventValue;
			String eventKey = receiveXml.getEventKey();
			if ("event".equals(receiveXml.getMsgType())) {
				eventKey = eventKey == null ? "" : eventKey.replace("qrscene_", "");
				eventValue = receiveXml.getMsgType() + "_" + eventKey;
			} else {
				eventValue = receiveXml.getMsgType();
			}
			Date today = new Date();
			String openId = receiveXml.getFromUserName();
			String key = String.format("WEIXIN_MENU_DAY_%s_%s_%s_%s", platform.getPlatformNo(), openId, eventValue,
					DateFormatUtils.format(today, DateConvertUtils.DATE_FORMAT));
			String result = redisDao.get(key);
			if (StringUtils.isNotBlank(result)) {
				return;
			}
			ygWeixinMenuDayService.saveMenuClick(openId, eventValue, platform.getPlatformNo());
			redisDao.set(key, openId, RedisConstant.EXPIRE_OF_DATE);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 取消关注公众号
	 * @param openId
	 * @param platform
	 */
	private void unsubscribe(String openId, YgPlatform platform) {
		proceeUserAuths(openId, platform, "unsubscribe");
	}

	/**
	 * 关注公众号
	 *
	 * @param openId
	 * @param platform
	 */
	private void subscribe(String openId, YgPlatform platform) {
		proceeUserAuths(openId, platform, "subscribe");
	}

	private void proceeUserAuths(String openId, YgPlatform platform, String remark) {
		EXECUTOR_SERVICE.execute(() -> {
			try {
				User wxUser = wxMpAuthorityService.getUserInfo(openId, platform.getPlatformNo());
				String unionId = wxUser.getUnionId();
				YgUserAuths userAuths = ygUserAuthsService.findByUnionIdAndPlatform(unionId, platform.getId());
				YgUserAuths record = new YgUserAuths();
				if (userAuths != null) {
					if (remark.equals(userAuths.getRemark())) {
						return;
					}
					record.setId(userAuths.getId());
				} else {
					record.setUnionId(unionId);
					record.setPlatformId(platform.getId());
					record.setAppType(AppType.WeChat);
					record.setIsRegister(Boolean.FALSE);
					YgUser user = ygUserService.findByUuid(unionId);
					if (user != null) {
						record.setUserId(user.getId());
					}
					record.setOpenId(openId);
					record.setCreateTime(new Date());
				}
				record.setRemark(remark);
				ygUserAuthsService.saveOrUpdate(record);
			} catch (WeixinException e) {
				e.printStackTrace();
			}
		});
	}

	/**
	 * 自动回复
	 * @return
	 */
	private void autoReply(String openId, YgPlatform platform) {
		YgPlatform smallPlatform = ygPlatformService.findByPlatformNo(platform.getPlatformNo(), AppType.Small);
		String content = "<a href=\"http://www.qq.com\" data-miniprogram-appid=\"" + smallPlatform.getAppId()
				+ "\" data-miniprogram-path=\"pages/coupon/coupon?id=412\">感谢您关注壹家店，点击领取优惠券</a>";
		wxMpMessageService.sendCustomMsg(new Text(content), openId, platform.getPlatformNo());
	}

	/**
	 * 加密消息
	 *
	 * @param outputXml
	 * @param timestamp
	 * @param nonce
	 * @return
	 * @throws AesException
	 */
	private String encryptMsg(String outputXml, String timestamp, String nonce, String appId, String token,
							  String encodingAESKey) throws AesException {
		MsgCrypt msgCrypt = new MsgCrypt(appId, encodingAESKey);
		outputXml = msgCrypt.encrypt(outputXml);

		if (StringUtils.isEmpty(timestamp)) {
			timestamp = Long.toString(System.currentTimeMillis());
		}
		// 生成安全签名
		String signature = SHA1Util.encode(token, timestamp, nonce, outputXml);
		// 组装xml字符串
		String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
				+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
				+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>";
		return String.format(format, outputXml, signature, timestamp, nonce);
	}

	/**
	 * 解密消息
	 *
	 * @param xmlData
	 * @param msgSignature
	 * @param timestamp
	 * @param nonce
	 * @return
	 * @throws AesException
	 */
	private String decryptMsg(String xmlData, String msgSignature, String timestamp, String nonce, String appId,
							  String token, String encodingAESKey) throws AesException {
		System.out.println("解密消息参数：xmlData=" + xmlData +", msgSignature=" + msgSignature +", " +
				"timestamp=" + timestamp + ", nonce=" + nonce + ", appId=" + appId + ", token=" + token + ", " +
				"encodingAESKey=" + encodingAESKey);
		// 解析xml字符串
		ReceiveXml receiveXml = XmlStream.fromXML(xmlData, ReceiveXml.class);
		String encrypt = receiveXml.getEncrypt();
		// 校验加密消息签名
		String temp = SHA1Util.encode(token, timestamp, nonce, encrypt);
		if (!msgSignature.equals(temp)) {
			throw new AesException(AesException.VALIDATE_SIGNATURE_ERROR);
		}
		// 解密消息
		MsgCrypt msgCrypt = new MsgCrypt(appId, encodingAESKey);
		String decryptMsg = msgCrypt.decryptMsg(encrypt);
		int index = decryptMsg.indexOf("</xml>") + 6;
		String fromAppId = decryptMsg.substring(index, decryptMsg.length());
		if (!appId.equals(fromAppId)) {
			throw new AesException(AesException.VALIDATE_APPID_ERROR);
		}
		xmlData = decryptMsg.substring(0, index);
		System.out.println("解密后的消息：" + xmlData);
		return xmlData;
	}
}